// The NSpring Framework for .NET
// Copyright (c) 2003, Jeffrey Varszegi
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification, 
// are permitted provided that the following conditions are met:
// 
// - Redistributions of source code must retain the above copyright notice, this 
// list of conditions and the following disclaimer.
// 
// - Redistributions in binary form must reproduce the above copyright notice, this 
// list of conditions and the following disclaimer in the documentation and/or other 
// materials provided with the distribution.
// 
// - Neither the name of the NSpring project nor the names of its contributors may 
// be used to endorse or promote products derived from this software without 
// specific prior written permission from the copyright owner.
// 
// - No product derived from this software may be called "NSpring", nor may 
// "NSpring" appear in the name of such a product, without specific prior written 
// permission from the copyright owner.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
// OF THE POSSIBILITY OF SUCH DAMAGE. 

namespace NSpring.Logging.Exceptions {

using System;
using System.Text;

using NSpring.Logging.EventFormatters;
using NSpring.Logging.XML;

/// <summary>
/// An implementation of the ExceptionFormatter class that formats logging exceptions
/// as XML
/// </summary>
public sealed class XMLExceptionFormatter : ExceptionFormatter {
    private const string VALID_XML_NAME_CHARACTERS      = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    
    private const string AMPERSAND_ESCAPE               = "&amp;";
    private const string APOSTROPHE_ESCAPE              = "&apos;";
    private const string LESS_THAN_ESCAPE               = "&lt;";
    private const string GREATER_THAN_ESCAPE            = "&gt;";
    private const string QUOTE_ESCAPE                   = "&quot;";
    
    private const string DEFAULT_DATE_FORMAT            = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffzzz";
    private const string Exception_START_TAG_END        = "\">";
    private const string LOGGER_START_TAG_END           = "\"/>";
    
    private const string INDENTATION                    = "   ";
    
    private readonly static string NEW_LINE_INDENTATION = NEW_LINE + INDENTATION;
    private const char T_CHAR                           = 'T';
    
    private string dateFormat                           = DEFAULT_DATE_FORMAT;
    private bool isDefaultDateFormat                    = true;
    
    private bool isEmptyTagInclusionEnabled             = false;

    private XMLFormatOptions xmlFormatOptions;

    private string exceptionListElementName             = "exceptions";
    private string exceptionElementName                 = "exception";
    private string timestampAttributeName               = "time";
    private string sourceAttributeName                  = "source";
    private string typeAttributeName                    = "type";
    private string loggerElementName                    = "logger";
    private string messageElementName                   = "text";
    private string stackTraceElementName                = "trace";
    
    private string exceptionListStartTag;
    private string exceptionListEndTag;
    private string exceptionStartTag;
    private string exceptionEndTag;
    private string loggerStartTag;
    private string messageStartTag;
    private string messageEndTag;
    private string messageEmptyTag;
    private string stackTraceStartTag;
    private string stackTraceEndTag;
    
    private string timestampAttribute;
    private string sourceAttribute;
    private string typeAttribute;

    private readonly object monitor                     = new object();
    
    public XMLExceptionFormatter() : base(new XMLEventFormatter()) {
        xmlFormatOptions = ((XMLEventFormatter)eventFormatter).XMLFormatOptions;
        Initialize();
    }
    
    /// <summary>
    /// Constructs a new instance using the specified formatting options
    /// </summary>
    /// <param name="_xmlFormatOptions">The formatting options to use in constructing XML</param>
    public XMLExceptionFormatter(XMLFormatOptions _xmlFormatOptions) : base(new XMLEventFormatter(_xmlFormatOptions)) {
        if (_xmlFormatOptions == null) {
            throw new ArgumentNullException();
        }
        xmlFormatOptions = _xmlFormatOptions;
        Initialize();
    }

    /// <summary>
    /// Gets and sets the XMLFormatOptions object used by this instance to construct
    /// names of elements and attributes.  This object controls aspects of casing, word
    /// separation in names, and indentation (pretty printing) in generated XML
    /// </summary>
    public XMLFormatOptions XMLFormatOptions {
        get {
            return xmlFormatOptions;
        }
        set {
            if (value == null) {
                throw new ArgumentNullException();
            }
            lock (monitor) {
                xmlFormatOptions = value;
                ((XMLEventFormatter)eventFormatter).XMLFormatOptions = xmlFormatOptions;
                Initialize();
            }
        }
    }
    
    protected override void FormatExceptionHeader(LoggerException e, StringBuilder sb) {
        sb.Append(exceptionStartTag);
        
        sb.Append(timestampAttribute);
        if (isDefaultDateFormat) {
            DateTime timestamp = DateTime.Now;
            sb.Append(yearStrings[timestamp.Year]).Append(HYPHEN_CHAR);
            sb.Append(dateStrings[timestamp.Month]).Append(HYPHEN_CHAR);
            sb.Append(dateStrings[timestamp.Day]).Append(T_CHAR);
            sb.Append(dateStrings[timestamp.Hour]).Append(COLON_CHAR);
            sb.Append(dateStrings[timestamp.Minute]).Append(COLON_CHAR);
            sb.Append(dateStrings[timestamp.Second]).Append(PERIOD_CHAR);
            sb.Append(millisecondStrings[timestamp.Millisecond]);
            sb.Append(TIME_ZONE);
        }
        else {
            sb.Append(DateTime.Now.ToString(dateFormat));
        }
        
        string source = e.Exception.Source;
        if (source != null) {
            sb.Append(sourceAttribute).Append(source);
        }
        
        sb.Append(typeAttribute).Append(e.Exception.GetType().FullName);
        
        sb.Append(Exception_START_TAG_END);
        
        if (xmlFormatOptions.IsIndentationEnabled) {
            sb.Append(NEW_LINE_INDENTATION);
        }
    }
    
    protected override void FormatExceptionFooter(LoggerException e, StringBuilder sb) {
        string s;
        if (xmlFormatOptions.IsIndentationEnabled) {
            sb.Append(NEW_LINE_INDENTATION);

            sb.Append(loggerStartTag);
            sb.Append(typeAttribute).Append(e.Logger.GetType().FullName);
            sb.Append(LOGGER_START_TAG_END);
            
            if (e.Exception.Message != null) {
                s = e.Exception.Message;
                if (s.IndexOf(AMPERSAND) >= 0) {
                    s = s.Replace(AMPERSAND, AMPERSAND_ESCAPE);
                }
                if (s.IndexOf(APOSTROPHE) >= 0) {
                    s = s.Replace(APOSTROPHE, APOSTROPHE_ESCAPE);
                }
                if (s.IndexOf(LESS_THAN) >= 0) {
                    s = s.Replace(LESS_THAN, LESS_THAN_ESCAPE);
                }
                if (s.IndexOf(GREATER_THAN) >= 0) {
                    s = s.Replace(GREATER_THAN, GREATER_THAN_ESCAPE);
                }
                if (s.IndexOf(QUOTE) >= 0) {
                    s = s.Replace(QUOTE, QUOTE_ESCAPE);
                }
                sb.Append(NEW_LINE_INDENTATION).Append(messageStartTag).Append(s).Append(messageEndTag);
            }
            else if (isEmptyTagInclusionEnabled) {
                sb.Append(NEW_LINE_INDENTATION).Append(messageEmptyTag);
            }
            
            s = e.Exception.StackTrace;
            if (s.IndexOf(AMPERSAND) >= 0) {
                s = s.Replace(AMPERSAND, AMPERSAND_ESCAPE);
            }
            if (s.IndexOf(APOSTROPHE) >= 0) {
                s = s.Replace(APOSTROPHE, APOSTROPHE_ESCAPE);
            }
            if (s.IndexOf(LESS_THAN) >= 0) {
                s = s.Replace(LESS_THAN, LESS_THAN_ESCAPE);
            }
            if (s.IndexOf(GREATER_THAN) >= 0) {
                s = s.Replace(GREATER_THAN, GREATER_THAN_ESCAPE);
            }
            if (s.IndexOf(QUOTE) >= 0) {
                s = s.Replace(QUOTE, QUOTE_ESCAPE);
            }
            sb.Append(NEW_LINE_INDENTATION).Append(stackTraceStartTag).Append(s).Append(stackTraceEndTag);
            sb.Append(NEW_LINE);
        }
        else {
            sb.Append(loggerStartTag);
            sb.Append(typeAttribute).Append(e.Logger.GetType().FullName);
            sb.Append(LOGGER_START_TAG_END);
            
            if (e.Exception.Message != null) {
                s = e.Exception.Message;
                if (s.IndexOf(AMPERSAND) >= 0) {
                    s = s.Replace(AMPERSAND, AMPERSAND_ESCAPE);
                }
                if (s.IndexOf(APOSTROPHE) >= 0) {
                    s = s.Replace(APOSTROPHE, APOSTROPHE_ESCAPE);
                }
                if (s.IndexOf(LESS_THAN) >= 0) {
                    s = s.Replace(LESS_THAN, LESS_THAN_ESCAPE);
                }
                if (s.IndexOf(GREATER_THAN) >= 0) {
                    s = s.Replace(GREATER_THAN, GREATER_THAN_ESCAPE);
                }
                if (s.IndexOf(QUOTE) >= 0) {
                    s = s.Replace(QUOTE, QUOTE_ESCAPE);
                }
                sb.Append(messageStartTag).Append(s).Append(messageEndTag);
            }
            else if (isEmptyTagInclusionEnabled) {
                sb.Append(messageEmptyTag);
            }
            
            s = e.Exception.StackTrace;
            if (s.IndexOf(AMPERSAND) >= 0) {
                s = s.Replace(AMPERSAND, AMPERSAND_ESCAPE);
            }
            if (s.IndexOf(APOSTROPHE) >= 0) {
                s = s.Replace(APOSTROPHE, APOSTROPHE_ESCAPE);
            }
            if (s.IndexOf(LESS_THAN) >= 0) {
                s = s.Replace(LESS_THAN, LESS_THAN_ESCAPE);
            }
            if (s.IndexOf(GREATER_THAN) >= 0) {
                s = s.Replace(GREATER_THAN, GREATER_THAN_ESCAPE);
            }
            if (s.IndexOf(QUOTE) >= 0) {
                s = s.Replace(QUOTE, QUOTE_ESCAPE);
            }
            sb.Append(stackTraceStartTag).Append(s).Append(stackTraceEndTag);
        }
        
        sb.Append(exceptionEndTag);
    }
    
    public override string GetExceptionListHeader() {
        return exceptionListStartTag;
    }
    
    public override string GetExceptionListFooter() {
        return exceptionListEndTag;
    }
    
    public override void Initialize() {
        lock (monitor) {
            XMLFormatOptions xfo  = xmlFormatOptions.Copy();
            
            string name               = xfo.GetFormattedElementName(exceptionListElementName);
            exceptionListStartTag     = "<" + name + ">";
            exceptionListEndTag       = "</" + name + ">";
            
            name                      = xfo.GetFormattedElementName(exceptionElementName);
            exceptionStartTag         = "<" + name;
            exceptionEndTag           = "</" + name + ">";
            
            name                      = xfo.GetFormattedElementName(loggerElementName);
            loggerStartTag            = "<" + name;
            
            name                      = xfo.GetFormattedElementName(messageElementName);
            messageStartTag           = "<" + name + ">";
            messageEndTag             = "</" + name + ">";
            messageEmptyTag           = "<" + name + "/>";
            
            name                      = xfo.GetFormattedElementName(stackTraceElementName);
            stackTraceStartTag        = "<" + name + ">";
            stackTraceEndTag          = "</" + name + ">";
            
            timestampAttribute        =   " " + xfo.GetFormattedAttributeName(timestampAttributeName)       + "=\"";
            sourceAttribute           = "\" " + xfo.GetFormattedAttributeName(sourceAttributeName)          + "=\"";
            typeAttribute             = "\" " + xfo.GetFormattedAttributeName(typeAttributeName)            + "=\"";
        }
    }
    
/*    
    private static string ValidateXMLName(string name) {
        if (name == null) {
            throw new ArgumentNullException();
        }
        name = name.Trim();
        if (name.Equals(BLANK_STRING)) {
            throw new ArgumentException("The name must contain characters");
        }
        name = name.Replace(HYPHEN, SPACE);
        name = name.Replace(UNDERSCORE, SPACE);
        for(int x = 0; x < name.Length; x++) {
            if (VALID_XML_NAME_CHARACTERS.IndexOf(name[x]) < 0) {
                throw new ArgumentException("Character " + name[x] + " not allowed");
            }
        }
        return name;
    }
*/

}

}
